{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
   "metadata": {},
   "source": [
    "# How to add thread-level persistence (functional API)\n",
    "\n",
    "!!! info \"Prerequisites\"\n",
    "\n",
    "    This guide assumes familiarity with the following:\n",
    "    \n",
    "    - [Functional API](../../concepts/functional_api/)\n",
    "    - [Persistence](../../concepts/persistence/)\n",
    "    - [Memory](../../concepts/memory/)\n",
    "    - [Chat Models](https://js.langchain.com/docs/concepts/chat_models/)\n",
    "\n",
    "Many AI applications need memory to share context across multiple interactions on the same [thread](../../concepts/persistence#threads) (e.g., multiple turns of a conversation). In LangGraph functional API, this kind of memory can be added to any [entrypoint()](/langgraphjs/reference/functions/langgraph.entrypoint-1.html) workflow using [thread-level persistence](/langgraphjs/concepts/persistence).\n",
    "\n",
    "When creating a LangGraph workflow, you can set it up to persist its results by using a [checkpointer](/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html):\n",
    "\n",
    "\n",
    "1. Create an instance of a checkpointer:\n",
    "\n",
    "    ```ts\n",
    "    import { MemorySaver } from \"@langchain/langgraph\";\n",
    "    \n",
    "    const checkpointer = new MemorySaver();\n",
    "    ```\n",
    "\n",
    "2. Pass `checkpointer` instance to the `entrypoint()` wrapper function:\n",
    "\n",
    "    ```ts\n",
    "    import { entrypoint } from \"@langchain/langgraph\";\n",
    "    const workflow = entrypoint({\n",
    "      name: \"workflow\",\n",
    "      checkpointer,\n",
    "    }, async (inputs) => {\n",
    "      ...\n",
    "    });\n",
    "    ```\n",
    "\n",
    "3. Retrieve `previous` state from the prior execution within the workflow:\n",
    "\n",
    "    ```ts\n",
    "    import { entrypoint, getPreviousState } from \"@langchain/langgraph\";\n",
    "    \n",
    "    const workflow = entrypoint({\n",
    "      name: \"workflow\",\n",
    "      checkpointer,\n",
    "    }, async (inputs) => {\n",
    "      const previous = getPreviousState();\n",
    "      const result = doSomething(previous, inputs);\n",
    "      ...\n",
    "    });\n",
    "    ```\n",
    "\n",
    "4. Optionally choose which values will be returned from the workflow and which will be saved by the checkpointer as `previous`:\n",
    "\n",
    "    ```ts\n",
    "    import { entrypoint, getPreviousState } from \"@langchain/langgraph\";\n",
    "    \n",
    "    const workflow = entrypoint({\n",
    "      name: \"workflow\",\n",
    "      checkpointer,\n",
    "    }, async (inputs) => {\n",
    "      const previous = getPreviousState();\n",
    "      const result = doSomething(previous, inputs);\n",
    "      ...\n",
    "      return entrypoint.final({\n",
    "        value: result,\n",
    "        save: combineState(inputs, result),\n",
    "      });\n",
    "    });\n",
    "    ```\n",
    "\n",
    "This guide shows how you can add thread-level persistence to your workflow.\n",
    "\n",
    "!!! tip \"Note\"\n",
    "\n",
    "    If you need memory that is __shared__ across multiple conversations or users (cross-thread persistence), check out this [how-to guide](../cross-thread-persistence-functional).\n",
    "\n",
    "!!! tip \"Note\"\n",
    "\n",
    "    If you need to add thread-level persistence to a `StateGraph`, check out this [how-to guide](../persistence)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "!!! note Compatibility\n",
    "\n",
    "    This guide requires `@langchain/langgraph>=0.2.42`.\n",
    "\n",
    "First, install the required dependencies for this example:\n",
    "\n",
    "```bash\n",
    "npm install @langchain/langgraph @langchain/anthropic @langchain/core zod\n",
    "```\n",
    "\n",
    "Next, we need to set API keys for Anthropic (the LLM we will use):\n",
    "\n",
    "```typescript\n",
    "process.env.ANTHROPIC_API_KEY = \"YOUR_API_KEY\";\n",
    "```\n",
    "\n",
    "!!! tip \"Set up [LangSmith](https://smith.langchain.com) for LangGraph development\"\n",
    "\n",
    "    Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4cf509bc",
   "metadata": {},
   "source": [
    "## Example: simple chatbot with short-term memory\n",
    "\n",
    "We will be using a workflow with a single task that calls a [chat model](https://js.langchain.com/docs/concepts/chat_models/).\n",
    "\n",
    "Let's first define the model we'll be using:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
   "metadata": {},
   "outputs": [],
   "source": [
    "import { ChatAnthropic } from \"@langchain/anthropic\";\n",
    "\n",
    "const model = new ChatAnthropic({\n",
    "  model: \"claude-3-5-sonnet-latest\",\n",
    "});"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b7a2792-982b-4e47-83eb-0c594725d1c1",
   "metadata": {},
   "source": [
    "Now we can define our task and workflow. To add in persistence, we need to pass in a [Checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver) to the [entrypoint()](/langgraphjs/reference/functions/langgraph.entrypoint-1.html) wrapper function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "87326ea6-34c5-46da-a41f-dda26ef9bd74",
   "metadata": {},
   "outputs": [],
   "source": [
    "import type { BaseMessage, BaseMessageLike } from \"@langchain/core/messages\";\n",
    "import {\n",
    "  addMessages,\n",
    "  entrypoint,\n",
    "  task,\n",
    "  getPreviousState,\n",
    "  MemorySaver,\n",
    "} from \"@langchain/langgraph\";\n",
    "\n",
    "const callModel = task(\"callModel\", async (messages: BaseMessageLike[]) => {\n",
    "  const response = model.invoke(messages);\n",
    "  return response;\n",
    "});\n",
    "\n",
    "const checkpointer = new MemorySaver();\n",
    "\n",
    "const workflow = entrypoint({\n",
    "  name: \"workflow\",\n",
    "  checkpointer,\n",
    "}, async (inputs: BaseMessageLike[]) => {\n",
    "  const previous = getPreviousState<BaseMessage>() ?? [];\n",
    "  const messages = addMessages(previous, inputs);\n",
    "  const response = await callModel(messages);\n",
    "  return entrypoint.final({\n",
    "    value: response,\n",
    "    save: addMessages(messages, response),\n",
    "  });\n",
    "});"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "250d8fd9-2e7a-4892-9adc-19762a1e3cce",
   "metadata": {},
   "source": [
    "If we try to use this workflow, the context of the conversation will be persisted across interactions."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7654ebcc-2179-41b4-92d1-6666f6f8634f",
   "metadata": {},
   "source": [
    "!!! note Note\n",
    "\n",
    "    If you're using LangGraph Cloud or LangGraph Studio, you __don't need__ to pass checkpointer to the `entrypoint` wrapper, since it's done automatically."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
   "metadata": {},
   "source": [
    "Here's how this works in practice:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cfd140f0-a5a6-4697-8115-322242f197b5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================== ai message ==============================\n",
      "Hi Bob! I'm Claude. Nice to meet you! How can I help you today?\n"
     ]
    }
   ],
   "source": [
    "const config = {\n",
    "  configurable: { thread_id: \"1\" },\n",
    "  streamMode: \"values\" as const,\n",
    "};\n",
    "const inputMessage = { role: \"user\", content: \"hi! I'm bob\" };\n",
    "\n",
    "const stream = await workflow.stream(\n",
    "  [inputMessage],\n",
    "  config,\n",
    ");\n",
    "\n",
    "for await (const chunk of stream) {\n",
    "  console.log(\"=\".repeat(30), `${chunk.getType()} message`, \"=\".repeat(30));\n",
    "  console.log(chunk.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1bb07bf8-68b7-4049-a0f1-eb67a4879a3a",
   "metadata": {},
   "source": [
    "You can always resume previous threads:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "08ae8246-11d5-40e1-8567-361e5bef8917",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================== ai message ==============================\n",
      "Your name is Bob - you just told me that in your first message.\n"
     ]
    }
   ],
   "source": [
    "const followupStream = await workflow.stream(\n",
    "  [{ role: \"user\", content: \"what's my name?\" }], \n",
    "  config,\n",
    ");\n",
    "\n",
    "for await (const chunk of followupStream) {\n",
    "  console.log(\"=\".repeat(30), `${chunk.getType()} message`, \"=\".repeat(30));\n",
    "  console.log(chunk.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f47bbfc-d9ef-4288-ba4a-ebbc0136fa9d",
   "metadata": {},
   "source": [
    "If we want to start a new conversation, we can pass in a different `thread_id`. Poof! All the memories are gone!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================== ai message ==============================\n",
      "I don't know your name as we just started chatting. Would you like to introduce yourself?\n"
     ]
    }
   ],
   "source": [
    "const newStream = await workflow.stream(\n",
    "  [{ role: \"user\", content: \"what's my name?\" }],\n",
    "  {\n",
    "    configurable: {\n",
    "      thread_id: \"2\",\n",
    "    },\n",
    "    streamMode: \"values\",\n",
    "  },\n",
    ");\n",
    "\n",
    "for await (const chunk of newStream) {\n",
    "  console.log(\"=\".repeat(30), `${chunk.getType()} message`, \"=\".repeat(30));\n",
    "  console.log(chunk.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac7926a8-4c88-4b16-973c-53d6da3f4a08",
   "metadata": {},
   "source": [
    "!!! tip \"Streaming tokens\"\n",
    "\n",
    "    If you would like to stream LLM tokens from your chatbot, you can use `streamMode: \"messages\"`. Check out this [how-to guide](../stream-tokens) to learn more."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
